package com.coocaa.ops.common.core.security.util;

import com.coocaa.ops.common.core.security.annotation.MyGuard;
import com.coocaa.ops.common.core.security.model.GuardModel;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.URLDecoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Enumeration;
import java.util.Map;
import java.util.TreeMap;

/**
 * @author bijiahao
 * @date : 2019/10/12.
 * @description
 */
public class SecurityUtil {

    /**
     * 请求参数签名验证
     *
     * @param appSecret
     * @param request
     * @return true 验证通过 false 验证失败
     * @throws Exception
     */
    public static boolean verifySign(String appSecret, HttpServletRequest request) throws Exception {
        /**
         * TreeMap中的元素默认按照keys的自然排序排列。
         * 对Integer来说，其自然排序就是数字的升序；对String来说，其自然排序就是按照字母表排序
         */
        TreeMap<String, String> params = new TreeMap<>();
        String signStr = request.getParameter("sign");
        if (StringUtils.isBlank(signStr)) {
            throw new RuntimeException("There is no signature field in the request parameter!");
        }
        Enumeration<String> enu = request.getParameterNames();
        while (enu.hasMoreElements()) {
            String paramName = enu.nextElement().trim();
            if (!"sign".equals(paramName)) {
                params.put(paramName, URLDecoder.decode(request.getParameter(paramName), "UTF-8"));
            }
        }
        return sign(appSecret, params).equals(signStr);
    }

    public static String getSign(String appSecret, GuardModel model) {
        TreeMap<String, String> params = new TreeMap<>();
        params.put("appSecret", appSecret);
        Field[] fields = null;
        Class<?> aClass = model.getClass();
        for (; aClass != Object.class; aClass = aClass.getSuperclass()) {
            fields = aClass.getDeclaredFields();
            handleFields(model, fields, params);
        }
        return sign(appSecret, params);
    }

    public static String getDecodeParam(String param) {
        try {
            return URLDecoder.decode(param, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return null;
        }
    }

    private static void handleFields(GuardModel model, Field[] fields, TreeMap<String, String> params) {
        try {
            for (Field field : fields) {
                field.setAccessible(true);
                MyGuard myGuard = field.getDeclaredAnnotation(MyGuard.class);
                if (null != myGuard && myGuard.require()) {
                    Object value = field.get(model);
                    if (null != value) {
                        if (value instanceof String[]) {
                            String[] values = (String[]) value;
                            params.put(field.getName(), getDecodeParam(values[0]));
                        } else {
                            params.put(field.getName(), getDecodeParam(String.valueOf(value)));
                        }
                    }
                }
            }
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * md5签名
     *
     * 按参数名称升序，将参数值进行连接 签名
     *
     * @param appSecret
     * @param params
     * @return
     */
    public static String sign(String appSecret, TreeMap<String, String> params) {
        StringBuilder paramValues = new StringBuilder();
        params.put("appSecret", appSecret);
        for (Map.Entry<String, String> entry : params.entrySet()) {
            paramValues.append(entry.getValue());
        }
        System.out.println(md5(paramValues.toString()));
        return md5(paramValues.toString());
    }


    private static String md5(String string) {
        byte[] hash;
        try {
            hash = MessageDigest.getInstance("MD5").digest(string.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("UTF-8 is unsupported", e);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("MessageDigest不支持MD5Util", e);
        }
        StringBuilder hex = new StringBuilder(hash.length * 2);
        for (byte b : hash) {
            if ((b & 0xFF) < 0x10) {
                hex.append("0");
            }
            hex.append(Integer.toHexString(b & 0xFF));
        }
        return hex.toString().toLowerCase();
    }
}
